//! Runtime library of [ArceOS](https://github.com/arceos-org/arceos).
//!
//! Any application uses ArceOS should link this library. It does some
//! initialization work before entering the application's `main` function.
//!
//! # Cargo Features
//!
//! - `alloc`: Enable global memory allocator.
//! - `paging`: Enable page table manipulation support.
//! - `irq`: Enable interrupt handling support.
//! - `multitask`: Enable multi-threading support.
//! - `smp`: Enable SMP (symmetric multiprocessing) support.
//! - `fs`: Enable filesystem support.
//! - `net`: Enable networking support.
//! - `display`: Enable graphics support.
//!
//! All the features are optional and disabled by default.

#![cfg_attr(not(test), no_std)]
#![feature(doc_auto_cfg)]

#[macro_use]
extern crate axlog;

#[cfg(feature = "driver-dyn")]
extern crate axklib_impl;

#[cfg(all(target_os = "none", not(test)))]
mod lang_items;

#[cfg(feature = "smp")]
mod mp;

#[cfg(feature = "smp")]
pub use self::mp::rust_main_secondary;

const LOGO: &str = r#"
       d8888                            .d88888b.   .d8888b.
      d88888                           d88P" "Y88b d88P  Y88b
     d88P888                           888     888 Y88b.
    d88P 888 888d888  .d8888b  .d88b.  888     888  "Y888b.
   d88P  888 888P"   d88P"    d8P  Y8b 888     888     "Y88b.
  d88P   888 888     888      88888888 888     888       "888
 d8888888888 888     Y88b.    Y8b.     Y88b. .d88P Y88b  d88P
d88P     888 888      "Y8888P  "Y8888   "Y88888P"   "Y8888P"
"#;

unsafe extern "C" {
    /// Application's entry point.
    fn main();
}

struct LogIfImpl;

#[crate_interface::impl_interface]
impl axlog::LogIf for LogIfImpl {
    fn console_write_str(s: &str) {
        axhal::console::write_bytes(s.as_bytes());
    }

    fn current_time() -> core::time::Duration {
        axhal::time::monotonic_time()
    }

    fn current_cpu_id() -> Option<usize> {
        #[cfg(feature = "smp")]
        if is_init_ok() {
            Some(axhal::percpu::this_cpu_id())
        } else {
            None
        }
        #[cfg(not(feature = "smp"))]
        Some(0)
    }

    fn current_task_id() -> Option<u64> {
        if is_init_ok() {
            #[cfg(feature = "multitask")]
            {
                axtask::current_may_uninit().map(|curr| curr.id().as_u64())
            }
            #[cfg(not(feature = "multitask"))]
            None
        } else {
            None
        }
    }
}

use core::sync::atomic::{AtomicUsize, Ordering};

static INITED_CPUS: AtomicUsize = AtomicUsize::new(0);

#[cfg(feature = "smp")]
fn is_init_ok() -> bool {
    INITED_CPUS.load(Ordering::Acquire) == axconfig::plat::CPU_NUM
}

#[cfg(not(feature = "smp"))]
fn is_init_ok() -> bool {
    true
}

/// The main entry point of the ArceOS runtime.
///
/// It is called from the bootstrapping code in the specific platform crate (see
/// [`axplat::main`]).
///
/// `cpu_id` is the logic ID of the current CPU, and `arg` is passed from the
/// bootloader (typically the device tree blob address).
///
/// In multi-core environment, this function is called on the primary core, and
/// secondary cores call [`rust_main_secondary`].
#[cfg_attr(not(test), axplat::main)]
pub fn rust_main(cpu_id: usize, arg: usize) -> ! {
    unsafe { axhal::mem::clear_bss() };
    axhal::init_percpu(cpu_id);
    axhal::init_early(cpu_id, arg);

    ax_println!("{}", LOGO);
    ax_println!(
        indoc::indoc! {"
            arch = {}
            platform = {}
            target = {}
            build_mode = {}
            log_level = {}
            backtrace = {}
            smp = {}
        "},
        axconfig::ARCH,
        axconfig::PLATFORM,
        option_env!("AX_TARGET").unwrap_or(""),
        option_env!("AX_MODE").unwrap_or(""),
        option_env!("AX_LOG").unwrap_or(""),
        axbacktrace::is_enabled(),
        axconfig::plat::CPU_NUM,
    );
    #[cfg(feature = "rtc")]
    ax_println!(
        "Boot at {}\n",
        chrono::DateTime::from_timestamp_nanos(axhal::time::wall_time_nanos() as _),
    );

    axlog::init();
    axlog::set_max_level(option_env!("AX_LOG").unwrap_or("")); // no effect if set `log-level-*` features
    info!("Logging is enabled.");
    info!("Primary CPU {} started, arg = {:#x}.", cpu_id, arg);

    axhal::mem::init();
    info!("Found physcial memory regions:");
    for r in axhal::mem::memory_regions() {
        info!(
            "  [{:x?}, {:x?}) {} ({:?})",
            r.paddr,
            r.paddr + r.size,
            r.name,
            r.flags
        );
    }

    #[cfg(feature = "alloc")]
    init_allocator();

    {
        use core::ops::Range;

        unsafe extern "C" {
            safe static _stext: [u8; 0];
            safe static _etext: [u8; 0];
            safe static _edata: [u8; 0];
        }

        let ip_range = Range {
            start: _stext.as_ptr() as usize,
            end: _etext.as_ptr() as usize,
        };

        let fp_range = Range {
            start: _edata.as_ptr() as usize,
            end: usize::MAX,
        };

        axbacktrace::init(ip_range, fp_range);
    }

    #[cfg(feature = "paging")]
    axmm::init_memory_management();

    #[cfg(feature = "driver-dyn")]
    axdriver::setup(arg);

    info!("Initialize platform devices...");
    axhal::init_later(cpu_id, arg);

    #[cfg(feature = "multitask")]
    axtask::init_scheduler();

    #[cfg(any(feature = "fs", feature = "net", feature = "display"))]
    {
        #[allow(unused_variables)]
        let mut all_devices = axdriver::init_drivers();

        #[cfg(feature = "fs")]
        {
            #[allow(unused_imports)]
            use axdriver::prelude::BaseDriverOps;

            axfs_ng::ROOT_FS_CONTEXT.call_once(|| {
                let dev = all_devices
                    .block
                    .take_one()
                    .expect("No block device found!");
                info!("Block device: {}", dev.device_name());
                let fs = axfs_ng::fs::new_default(dev).expect("Failed to initialize filesystem");
                let mount = axfs_ng_vfs::Mountpoint::new_root(&fs);
                axfs_ng::FsContext::new(mount.root_location())
            });
        }

        #[cfg(feature = "net")]
        axnet::init_network(all_devices.net);

        #[cfg(feature = "display")]
        axdisplay::init_display(all_devices.display);

        #[cfg(feature = "input")]
        axinput::init_input(all_devices.input);
    }

    #[cfg(feature = "smp")]
    self::mp::start_secondary_cpus(cpu_id);

    #[cfg(feature = "irq")]
    {
        info!("Initialize interrupt handlers...");
        init_interrupt();
    }

    #[cfg(all(feature = "tls", not(feature = "multitask")))]
    {
        info!("Initialize thread local storage...");
        init_tls();
    }

    info!("Primary CPU {} init OK.", cpu_id);
    INITED_CPUS.fetch_add(1, Ordering::Release);

    while !is_init_ok() {
        core::hint::spin_loop();
    }

    unsafe { main() };

    #[cfg(feature = "multitask")]
    axtask::exit(0);
    #[cfg(not(feature = "multitask"))]
    {
        debug!("main task exited: exit_code={}", 0);
        axhal::power::system_off();
    }
}

#[cfg(feature = "alloc")]
fn init_allocator() {
    use axhal::mem::{MemRegionFlags, memory_regions, phys_to_virt};

    info!("Initialize global memory allocator...");
    info!("  use {} allocator.", axalloc::global_allocator().name());

    const MAX_PADDR: usize = 0x1_0000_0000;

    let mut max_region_size = 0;
    let mut max_region_paddr = None;
    let mut use_next_free = false;

    for r in memory_regions() {
        if r.name == ".bss" {
            use_next_free = true;
        } else if r.flags.contains(MemRegionFlags::FREE) {
            if r.paddr.as_usize() >= MAX_PADDR {
                continue;
            }
            
            if use_next_free {
                max_region_paddr = Some(r.paddr);
                break;
            } else if r.size > max_region_size {
                max_region_size = r.size;
                max_region_paddr = Some(r.paddr);
            }
        }
    }

    if let Some(paddr) = max_region_paddr {
        for r in memory_regions() {
            if r.flags.contains(MemRegionFlags::FREE) && r.paddr == paddr {
                let usable_size = if r.paddr.as_usize() + r.size > MAX_PADDR {
                    MAX_PADDR - r.paddr.as_usize()
                } else {
                    r.size
                };
                
                info!("  Initialize heap at [PA:{:#x}, PA:{:#x}), size: {} MB", 
                      r.paddr.as_usize(), r.paddr.as_usize() + usable_size, 
                      usable_size / 1024 / 1024);
                axalloc::global_init(phys_to_virt(r.paddr).as_usize(), usable_size);
                break;
            }
        }

        for r in memory_regions() {
            if r.flags.contains(MemRegionFlags::FREE) 
                && r.paddr != paddr 
                && r.paddr.as_usize() < MAX_PADDR {

                let usable_size = if r.paddr.as_usize() + r.size > MAX_PADDR {
                    MAX_PADDR - r.paddr.as_usize()
                } else {
                    r.size
                };
                
                info!("  Add heap region [PA:{:#x}, PA:{:#x}), size: {} MB",
                      r.paddr.as_usize(), r.paddr.as_usize() + usable_size,
                      usable_size / 1024 / 1024);
                axalloc::global_add_memory(phys_to_virt(r.paddr).as_usize(), usable_size)
                    .expect("add heap memory region failed");
            }
        }
    }
}

#[cfg(feature = "irq")]
fn init_interrupt() {
    // Setup timer interrupt handler
    const PERIODIC_INTERVAL_NANOS: u64 =
        axhal::time::NANOS_PER_SEC / axconfig::TICKS_PER_SEC as u64;

    #[percpu::def_percpu]
    static NEXT_DEADLINE: u64 = 0;

    fn update_timer() {
        let now_ns = axhal::time::monotonic_time_nanos();
        // Safety: we have disabled preemption in IRQ handler.
        let mut deadline = unsafe { NEXT_DEADLINE.read_current_raw() };
        if now_ns >= deadline {
            deadline = now_ns + PERIODIC_INTERVAL_NANOS;
        }
        unsafe { NEXT_DEADLINE.write_current_raw(deadline + PERIODIC_INTERVAL_NANOS) };
        axhal::time::set_oneshot_timer(deadline);
    }

    axhal::irq::register(axconfig::devices::TIMER_IRQ, |_| {
        update_timer();
        #[cfg(feature = "multitask")]
        axtask::on_timer_tick();
    });

    #[cfg(feature = "ipi")]
    axhal::irq::register(axhal::irq::IPI_IRQ, |_| {
        axipi::ipi_handler();
    });

    // Enable IRQs before starting app
    axhal::asm::enable_irqs();
}

#[cfg(all(feature = "tls", not(feature = "multitask")))]
fn init_tls() {
    let main_tls = axhal::tls::TlsArea::alloc();
    unsafe { axhal::asm::write_thread_pointer(main_tls.tls_ptr() as usize) };
    core::mem::forget(main_tls);
}
